#include "./log_uart.h"
#include "../device/dma.h"
#include "../device/gpio.h"
#include "../rtos/event_flags.h"
#include <string.h>

namespace driver {

using namespace device;

#define TX_STREAM   DMA1_Stream7
#define RX_STREAM   DMA1_Stream0

using TX = PC<12>;
using RX = PD<2>;
using TXDMA = Dma<1, 7>;
using RXDMA = Dma<1, 0>;
static constexpr uint8_t TXChan = 4;
static constexpr uint8_t RXChan = 4;

LogUart::LogUart() {

    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN + RCC_AHB1ENR_GPIODEN + RCC_AHB1ENR_DMA1EN;
    RCC->APB1ENR |= RCC_APB1ENR_UART5EN;

    ConfigAf<8, TX, RX>();

    UART5->CR1 = 0x00;
    UART5->CR2 = 0x00;
    UART5->CR3 = 0xC0;
    UART5->BRR = 42000000 / 115200;
    UART5->CR1 = 0x201C;

    RXDMA::ClrFlag();
    RX_STREAM->CR = 0x00;
    RX_STREAM->M0AR = reinterpret_cast<uint32_t>(m_recv_buf);
    RX_STREAM->PAR = reinterpret_cast<uint32_t>(&UART5->DR);
    RX_STREAM->NDTR = RecvBuffSize;
    RX_STREAM->CR = (RXChan << 25) +
        (0x01 << 10) +
        (0x01 << 8) +
        (0x01);

    rtos::flags::global->set(rtos::flags::LogUartTxFlag);

    NVIC_EnableIRQ(UART5_IRQn);
    NVIC_EnableIRQ(DMA1_Stream7_IRQn);
}

void LogUart::write_by_dma(const void *buf, uint32_t len) {
    TXDMA::ClrFlag();
    TX_STREAM->CR = 0x00;
    TX_STREAM->M0AR = reinterpret_cast<uint32_t>(buf);
    TX_STREAM->PAR = reinterpret_cast<uint32_t>(&UART5->DR);
    TX_STREAM->NDTR = len;
    TX_STREAM->CR = (TXChan << 25) +
        (0x01 << 10) +
        (0x01 << 6) +
        (0x01 << 4) +
        (0x01);
}

void LogUart::wait_write_complete() {
    rtos::flags::global->wait_any(rtos::flags::LogUartTxFlag);
}

void LogUart::on_recv_idle() {
    uint32_t index = RecvBuffSize - RX_STREAM->NDTR;
    if (index == m_recv_index) {
        return;
    }
    utils::byte_view_t bw;
    if (index > m_recv_index) {
        bw.buf = m_recv_buf + m_recv_index;
        bw.len = index - m_recv_index;
    } else {
        uint32_t k = RecvBuffSize - m_recv_index;
        memcpy(m_copy_buf, m_recv_buf + m_recv_index, k);
        memcpy(m_copy_buf + k, m_recv_buf, index);
        bw.buf = m_copy_buf;
        bw.len = k + index;
    }
    m_recv_index = index;
    m_mq.post(bw);
}

}

using namespace driver;

extern "C"
void DMA1_Stream7_IRQHandler() {
    if (!TXDMA::IsTC()) {
        return;
    }
    TXDMA::ClrFlag();
    rtos::flags::global->set(rtos::flags::LogUartTxFlag);
}

extern "C"
void UART5_IRQHandler() {
    if ((UART5->SR & USART_SR_IDLE) != 0) {
        uint32_t clr = UART5->DR;
        logUart->on_recv_idle();
        return;
    }
}
